/**
 * Static interface for types which can store their values as packed
 * elements in an array.  These methods and properties are expected to be
 * defined on a constructor function.
 *
 * @interface Packable
 *
 * @see PackableForInterpolation
 */

import { DeveloperError } from './DeveloperError'
import { Check } from './Check'
import { defined } from './Defined'
import { UNION_NUMBER_ARRAY } from './HDataType'

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const Packable12 = {
  /**
   * 将对象的值打包到数组中，占用的位数。
   * The number of elements used to pack the object into an array.
   * @type {number}
   */
  packedLength: undefined,

  /**
   * Stores the provided instance into the provided array.
   * @function
   *
   * @param {*} value The value to pack.
   * @param {number[]} array The array to pack into.
   * @param {number} [startingIndex=0] The index into the array at which to start packing the elements.
   */
  pack: DeveloperError.throwInstantiationError,

  /**
   * Retrieves an instance from a packed array.
   * @function
   *
   * @param {number[]} array The packed array.
   * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
   * @param {object} [result] The object into which to store the result.
   * @returns {object} The modified result parameter or a new Object instance if one was not provided.
   */
  unpack: DeveloperError.throwInstantiationError,
}
/**
 * 用于支持插值的Packable接口的对象实现
 */
export interface IPackable {
  /**
   * 将对象的值打包到数组中，占用的位数。
   * The number of elements used to pack the object into an array.
   * @type {number}
   */
  getPackedLength: () => number
  getRowLength: () => number
  getColumnLength: () => number
  /**
   * Stores the provided self instance into the provided array.
   * @function
   *
   * @param {number[]} array The array to pack into.
   * @param {number} [startingIndex=0] The index into the array at which to start packing the elements.
   */
  pack: (array: UNION_NUMBER_ARRAY, startingIndex?: number) => void
  /**
   * Retrieves an instance from a packed array.
   * @function
   *
   * @param {number[]} array The packed array.
   * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
   */
  unpack: (array: UNION_NUMBER_ARRAY, startingIndex?: number) => void
}
/**
 * 用于支持Packable接口的对象实现 用于数据对象
 * 的数组输出
 */
export function packs(
  value: IPackable,
  array: UNION_NUMBER_ARRAY,
  startingIndex?: number,
): UNION_NUMBER_ARRAY {
  startingIndex = startingIndex ?? 0
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.object('value', value)
  Check.defined('array', array)
  //>>includeEnd('debug');
  value.pack(array, startingIndex)
  return array
}
export function unpacks(
  array: UNION_NUMBER_ARRAY,
  startingIndex: number,
  result: IPackable,
): IPackable {
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.object('result', result)
  Check.defined('array', array)
  Check.defined('result', result)
  //>>includeEnd('debug');
  result.unpack(array, startingIndex)
  return result
}

export function getArrayPackedLength(array: Array<IPackable>): number {
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.object('array', array)
  Check.defined('array', array)
  //>>includeEnd('debug');
  let length = 0
  for (const element of array) {
    length += element.getPackedLength()
  }
  return length
}
export function getMinArrayPackedLength(array: Array<IPackable>): number {
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.object('array', array)
  Check.defined('array', array)
  //>>includeEnd('debug');
  let min: number = 0
  let element_pack_len = 0
  for (const element of array) {
    element_pack_len = element.getPackedLength()
    if (min == 0) {
      min = element_pack_len
    } else if (min > element_pack_len) {
      min = element_pack_len
    }
  }
  return min
}
/**
 * 将一组对象轴展平为一组组件。
 *
 * @param {IPackable[]} array The array of cartesians to pack.
 * @param {number[]} [result] The array onto which to store the result. If this is a typed array, it must have array.length * 2 components, else a {@link DeveloperError} will be thrown. If it is a regular array, it will be resized to have (array.length * 2) elements.
 * @returns {number[]} The packed array.
 */
export function packArray(
  array: Array<IPackable>,
  result: UNION_NUMBER_ARRAY,
  startIndex: number = 0): UNION_NUMBER_ARRAY {
  //>>includeStart('debug', pragmas.debug);
  Check.defined('array', array)
  //>>includeEnd('debug');
  const length = array.length
  const resultLength = getArrayPackedLength(array)
  if (!defined(result)) {
    result = new Array(resultLength)
  } else if (!Array.isArray(result)) {
    //>>includeStart('debug', pragmas.debug);
    throw new DeveloperError(
      'If result is a typed array, it must have exactly ${resultLength} elements',
    )
    //>>includeEnd('debug');
  } else if (result.length !== resultLength) {
    result.length = resultLength
  }
  let j = startIndex
  for (let i = 0; i < length; ++i) {
    packs(array[i], result, j)
    const packedLength = array[i].getPackedLength()
    if (packedLength <= 0) {
      break
    }
    j += array[i].getPackedLength()
    if (j >= resultLength) {
      break
    }
  }
  return result
}

/**
 * 将一维笛卡尔分量数组解包为一维Cartesian2分量数组。
 *
 * @param {UNION_NUMBER_ARRAY} array The array of components to unpack.
 * @param {IPackable[]} [result] The array onto which to store the result.
 * @returns {IPackable[]} The unpacked array.
 */
export function unpackArray(
  array: UNION_NUMBER_ARRAY,
  result: Array<IPackable> | undefined,
): Array<IPackable> {
  //>>includeStart('debug', pragmas.debug);
  Check.defined('array', array)
  Check.defined('result', result)
  Check.typeOf.object('result', result)
  //>>includeEnd('debug');
  const packlengths = getArrayPackedLength(result!)
  const minpacklengths = getMinArrayPackedLength(result!)
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.tnumber.greaterThanOrEquals('array.length', array.length, packlengths)
  if (array.length % minpacklengths !== 0) {
    throw new DeveloperError('array length must be a multiple of${minpacklengths} .')
  }
  //>>includeEnd('debug');
  const length = array.length
  if (!defined(result)) {
    throw new DeveloperError('result must be an array of IPackable.')
  } else {
    result!.length = length / minpacklengths
  }
  let j = 0
  for (let i = 0; i < length; ) {
    unpacks(array, i, result![j])
    //result![j].unpack(array, i)
    const packedLength = result![j].getPackedLength()
    if (packedLength <= 0) {
      break
    }
    j++
    if (j >= result!.length) {
      break
    }
    i += packedLength
  }
  return result!
}
